/**
 * @file
 * Drupal to Google Maps API bridge.
 */

/*global jQuery, Drupal, GLatLng, GSmallZoomControl, GLargeMapControl, GMap2 */
/*global GMapTypeControl, GSmallMapControl, G_HYBRID_MAP, G_NORMAL_MAP */
/*global G_PHYSICAL_MAP, G_SATELLITE_MAP, GHierarchicalMapTypeControl */
/*global GKeyboardHandler, GLatLngBounds, GMenuMapTypeControl, GEvent */
/*global GOverviewMapControl, GScaleControl, GUnload */

(function () { // BEGIN closure
    var handlers = {};
    var maps = {};
    var ajaxoffset = 0;

    Drupal.gmap = {

        /**
         * Retrieve a map object for use by a non-widget.
         * Use this if you need to be able to fire events against a certain map
         * which you have the mapid for.
         * Be a good GMap citizen! Remember to send change()s after modifying variables!
         */
        getMap: function (mapid) {
            if (maps[mapid]) {
                return maps[mapid];
            }
            else {
                // Perhaps the user passed a widget id instead?
                mapid = mapid.split('-').slice(1, -1).join('-');
                if (maps[mapid]) {
                    return maps[mapid];
                }
            }
            return false;
        },

        unloadMap: function (mapid) {
            delete maps[mapid];
        },

        addHandler: function (handler, callback) {
            if (!handlers[handler]) {
                handlers[handler] = [];
            }
            handlers[handler].push(callback);
        },

        globalChange: function (name, userdata) {
            for (var mapid in Drupal.settings.gmap) {
                if (Drupal.settings.gmap.hasOwnProperty(mapid)) {
                    // Skip maps that are set up but not shown, etc.
                    if (maps[mapid]) {
                        maps[mapid].change(name, -1, userdata);
                    }
                }
            }
        },

        setup: function (settings) {
            var obj = this;

            var initcallback = function (mapid) {
                return (function () {
                    maps[mapid].change("bootstrap_options", -1);
                    maps[mapid].change("boot", -1);
                    maps[mapid].change("init", -1);
                    // Send some changed events to fire up the rest of the initial settings..
                    maps[mapid].change("maptypechange", -1);
                    maps[mapid].change("controltypechange", -1);
                    maps[mapid].change("alignchange", -1);
                    // Set ready to put the event system into action.
                    maps[mapid].ready = true;
                    maps[mapid].change("ready", -1);
                });
            };

            if (settings || (Drupal.settings && Drupal.settings.gmap)) {
                var mapid = obj.id.split('-');
                if (Drupal.settings['gmap_remap_widgets']) {
                    if (Drupal.settings['gmap_remap_widgets'][obj.id]) {
                        jQuery.each(Drupal.settings['gmap_remap_widgets'][obj.id].classes, function () {
                            jQuery(obj).addClass(this);
                        });
                        mapid = Drupal.settings['gmap_remap_widgets'][obj.id].id.split('-');
                    }
                }
                var instanceid = mapid.pop();
                mapid.shift();
                mapid = mapid.join('-');
                var control = instanceid.replace(/\d+$/, '');

                // Lazy init the map object.
                if (!maps[mapid]) {
                    if (settings) {
                        maps[mapid] = new Drupal.gmap.map(settings);
                    }
                    else {
                        maps[mapid] = new Drupal.gmap.map(Drupal.settings.gmap[mapid]);
                    }
                    // Prepare the initialization callback.
                    var callback = initcallback(mapid);
                    setTimeout(callback, 0);
                }

                if (handlers[control]) {
                    for (var i = 0; i < handlers[control].length; i++) {
                        handlers[control][i].call(maps[mapid], obj);
                    }
                }
                else {
                    // Element with wrong class?
                }
            }
        }
    };

    jQuery.fn.createGMap = function (settings, mapid) {
        return this.each(function () {
            if (!mapid) {
                mapid = 'auto' + ajaxoffset + 'ajax';
                ajaxoffset++;
            }
            settings.id = mapid;
            jQuery(this)
                .attr('id', 'gmap-' + mapid + '-gmap0')
                .css('width', settings.width)
                .css('height', settings.height)
                .addClass('gmap-control')
                .addClass('gmap-gmap')
                .addClass('gmap')
                .addClass('gmap-map')
                .addClass('gmap-' + mapid + '-gmap')
                .addClass('gmap-processed')
                .each(function () {
                    Drupal.gmap.setup.call(this, settings)
                });
        });
    };

})(); // END closure

Drupal.gmap.factory = {};

Drupal.gmap.map = function (v) {
    this.vars = v;
    this.map = undefined;
    this.ready = false;
    var _bindings = {};

    /**
     * Register interest in a change.
     */
    this.bind = function (name, callback) {
        if (!_bindings[name]) {
            _bindings[name] = [];
        }
        return _bindings[name].push(callback) - 1;
    };

    /**
     * Change notification.
     * Interested parties can act on changes.
     */
    this.change = function (name, id, userdata) {
        var c;
        if (_bindings[name]) {
            for (c = 0; c < _bindings[name].length; c++) {
                if (c !== id) {
                    _bindings[name][c](userdata);
                }
            }
        }
        if (name !== 'all') {
            this.change('all', -1, name, userdata);
        }
    };

    /**
     * Deferred change notification.
     * This will cause a change notification to be tacked on to the *end* of the event queue.
     */
    this.deferChange = function (name, id, userdata) {
        var obj = this;
        // This will move the function call to the end of the event loop.
        setTimeout(function () {
            obj.change(name, id, userdata);
        }, 0);
    };

    this.getMapTypeName = function (type) {
        if (type == 'map' || type == 'roadmap') return 'Map';
        if (type == 'hybrid') return 'Hybrid';
        if (type == 'physical' || type == 'terrain') return 'Physical';
        if (type == 'satellite') return 'Satellite';
    };

    this.getMapTypeId = function (type) {
        if (type == 'Map' || type == 'Roadmap') return google.maps.MapTypeId.ROADMAP;
        if (type == 'Hybrid') return google.maps.MapTypeId.HYBRID;
        if (type == 'Physical' || type == 'Terrain') return google.maps.MapTypeId.TERRAIN;
        if (type == 'Satellite') return google.maps.MapTypeId.SATELLITE;
    };
};

////////////////////////////////////////
//             Map widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('gmap', function (elem) {
    var obj = this;
    var _ib = {};

    // Respond to incoming zooms
    _ib.zoom = obj.bind("zoom", function (zoom) {
        obj.map.setZoom(obj.vars.zoom);
    });

    // Respond to incoming moves
    _ib.move = obj.bind("move", function () {
        obj.map.panTo(new google.maps.LatLng(obj.vars.latitude, obj.vars.longitude));
    });

    // Respond to incoming width changes.
    _ib.width = obj.bind("widthchange", function (w) {
        obj.map.getDiv().style.width = w;
        google.maps.event.trigger(obj.map);
    });
    // Send out outgoing width changes.
    // N/A
    // Respond to incoming height changes.
    _ib.height = obj.bind("heightchange", function (h) {
        obj.map.getDiv().style.height = h;
        google.maps.event.trigger(obj.map);
    });
    // Send out outgoing height changes.
    // N/A

    // Respond to incoming control type changes.
    _ib.ctc = obj.bind("controltypechange", function () {
        if (obj.vars.controltype === 'Small') {
            obj.map.setOptions({zoomControlOptions: {style: google.maps.ZoomControlStyle.SMALL}});
        }
        else if (obj.vars.controltype === 'Large') {
            obj.map.setOptions({zoomControlOptions: {style: google.maps.ZoomControlStyle.LARGE}});
        }
        // obsolete
        else if (obj.vars.controltype === 'Android') {
            obj.map.setOptions({zoomControlOptions: {style: google.maps.ZoomControlStyle.SMALL}});
        }
    });
    // Send out outgoing control type changes.
    // N/A

    // Respond to incoming map type changes.
    _ib.mtc = obj.bind("maptypechange", function () {
        obj.map.setMapTypeId(obj.getMapTypeId(obj.vars.maptype));
    });
    // Send out outgoing map type changes.
    // N/A

    obj.bind("bootstrap_options", function () {
        // Bootup options.
        var opts = {}; // Object literal google.maps.MapOptions
        obj.opts = opts;

        // Disable default UI for custom options
        opts.disableDefaultUI = true;

        // Set draggable property
        if (obj.vars.behavior.nodrag) {
            opts.draggable = false;
        }
        else if (obj.vars.behavior.nokeyboard) {
            opts.keyboardShortcuts = false;
        }

        // Set default map type (set to road map if nothing selected)
        switch (obj.vars.maptype) {
            case 'Hybrid':
                opts.mapTypeId = google.maps.MapTypeId.HYBRID;
                break;
            case 'Physical':
                opts.mapTypeId = google.maps.MapTypeId.TERRAIN;
                break;
            case 'Satellite':
                opts.mapTypeId = google.maps.MapTypeId.SATELLITE;
                break;
            case 'Map':
            default:
                opts.mapTypeId = google.maps.MapTypeId.ROADMAP;
                break;
        }

        // Null out the enabled types.
        opts.mapTypeIds = [];

        if (obj.vars.baselayers.Map) {
            opts.mapTypeIds.push(google.maps.MapTypeId.ROADMAP);
        }
        if (obj.vars.baselayers.Hybrid) {
            opts.mapTypeIds.push(google.maps.MapTypeId.HYBRID);
        }
        if (obj.vars.baselayers.Physical) {
            opts.mapTypeIds.push(google.maps.MapTypeId.TERRAIN);
        }
        if (obj.vars.baselayers.Satellite) {
            opts.mapTypeIds.push(google.maps.MapTypeId.SATELLITE);
        }

        if (obj.vars.draggableCursor) {
            opts.draggableCursor = obj.vars.draggableCursor;
        }
        if (obj.vars.draggingCursor) {
            opts.draggingCursor = obj.vars.draggingCursor;
        }
        if (obj.vars.backgroundColor) {
            opts.backgroundColor = obj.vars.backgroundColor;
        }

        // Map type control
        opts.mapTypeControl = true;
        opts.mapTypeControlOptions = {};
        if (obj.vars.mtc === 'standard') {
            opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.DEFAULT;
        }
        else if (obj.vars.mtc === 'horiz') {
            opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.HORIZONTAL_BAR;
        }
        else if (obj.vars.mtc === 'menu') {
            opts.mapTypeControlOptions.style = google.maps.MapTypeControlStyle.DROPDOWN_MENU;
        }
        else if (obj.vars.mtc === 'none') {
            opts.mapTypeControl = false;
        }

        // Navigation control type
        if (obj.vars.controltype !== 'None') {
            opts.zoomControl = true;
        }
        if (obj.vars.pancontrol) {
            opts.panControl = true;
        }
        if (obj.vars.streetviewcontrol) {
            opts.streetViewControl = true;
        }
        if (obj.vars.controltype === 'Small') {
            obj.zoomControlOptions = {style: google.maps.ZoomControlStyle.SMALL};
        }
        else if (obj.vars.controltype === 'Large') {
            obj.zoomControlOptions = {style: google.maps.ZoomControlStyle.LARGE};
        }


        // Set scale control visibility
        opts.scaleControl = obj.vars.behavior.scale;

        // Scroll wheel control
        if (obj.vars.behavior.nomousezoom) {
            opts.scrollwheel = false;
        }
        // Disable double-click zoom
        if (obj.vars.behavior.nocontzoom) {
            opts.disableDoubleClickZoom = true;
        }
        // Overview Map
        if (obj.vars.behavior.overview) {
            opts.overviewMapControl = true;
            opts.overviewMapControlOptions = {opened: true};
        }

    });

    obj.bind("boot", function () {
        obj.map = new google.maps.Map(elem, obj.opts);
        //console.log(obj.map);
    });

    obj.bind("init", function () {
        var map = obj.map;

        // Not implemented in API v3
        // if (obj.vars.behavior.overview) {
        //   map.addControl(new GOverviewMapControl());
        // }
        // if (obj.vars.behavior.googlebar) {
        //   map.enableGoogleBar();
        // }

        if (obj.vars.extent) {
            var c = obj.vars.extent;
            var extent = new google.maps.LatLngBounds(new google.maps.LatLng(c[0][0], c[0][1]), new google.maps.LatLng(c[1][0], c[1][1]));
            obj.vars.latitude = extent.getCenter().lat();
            obj.vars.longitude = extent.getCenter().lng();
            obj.vars.zoom = map.getBoundsZoomLevel(extent);
        }
        if (obj.vars.behavior.collapsehack) {
            // Modify collapsable fieldsets to make maps check dom state when the resize handle
            // is clicked. This may not necessarily be the correct thing to do in all themes,
            // hence it being a behavior.
            setTimeout(function () {
                var r = function () {
                    var coord = map.getCenter();
                    google.maps.event.trigger(map, "resize");
                    map.setCenter(new google.maps.LatLng(coord.lat(), coord.lng()), obj.vars.zoom);
                };
                jQuery(elem).parents('fieldset.collapsible').children('legend').children('a').click(r);
                jQuery('.vertical-tab-button', jQuery(elem).parents('.vertical-tabs')).children('a').click(r);
                jQuery(window).bind('hashchange', r);
                // Would be nice, but doesn't work.
                //$(elem).parents('fieldset.collapsible').children('.fieldset-wrapper').scroll(r);
            }, 0);
        }
        map.setCenter(new google.maps.LatLng(obj.vars.latitude, obj.vars.longitude));
        map.setZoom(obj.vars.zoom);

        // Send out outgoing zooms
        google.maps.event.addListener(map, "zoom_changed", function () {
            obj.vars.zoom = map.getZoom();
            obj.change("zoom", _ib.zoom);
        });

        // Send out outgoing moves
        google.maps.event.addListener(map, "center_changed", function () {
            var coord = map.getCenter();
            obj.vars.latitude = coord.lat();
            obj.vars.longitude = coord.lng();
            obj.change("move", _ib.move);
        });

        // Send out outgoing map type changes.
        google.maps.event.addListener(map, "maptypeid_changed", function () {
            // If the map isn't ready yet, ignore it.
            if (obj.ready) {
                obj.vars.maptype = obj.getMapTypeName(map.getMapTypeId());
                obj.change("maptypechange", _ib.mtc);
            }
        });

        /*
         google.maps.event.addListener(map, 'click', function(event) {
         alert(Drupal.gmap.getIcon("big blue", 0));
         var marker = new google.maps.Marker({
         position: event.latLng,
         map: map
         });
         google.maps.event.addListener(marker, 'click', function() {
         marker.setMap(null);
         });
         });
         */
    });
});

////////////////////////////////////////
//            Zoom widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('zoom', function (elem) {
    var obj = this;
    // Respond to incoming zooms
    var binding = obj.bind("zoom", function () {
        elem.value = obj.vars.zoom;
    });
    // Send out outgoing zooms
    jQuery(elem).change(function () {
        obj.vars.zoom = parseInt(elem.value, 10);
        obj.change("zoom", binding);
    });
});

////////////////////////////////////////
//          Latitude widget           //
////////////////////////////////////////
Drupal.gmap.addHandler('latitude', function (elem) {
//  var obj = this;
//  // Respond to incoming movements.
//  var binding = obj.bind("move", function () {
//    elem.value = '' + obj.vars.latitude;
//  });
//  // Send out outgoing movements.
//  $(elem).change(function () {
//    obj.vars.latitude = Number(this.value);
//    obj.change("move", binding);
//  });
});

////////////////////////////////////////
//         Longitude widget           //
////////////////////////////////////////
Drupal.gmap.addHandler('longitude', function (elem) {
//  var obj = this;
//  // Respond to incoming movements.
//  var binding = obj.bind("move", function () {
//    elem.value = '' + obj.vars.longitude;
//  });
//  // Send out outgoing movements.
//  $(elem).change(function () {
//    obj.vars.longitude = Number(this.value);
//    obj.change("move", binding);
//  });
});

////////////////////////////////////////
//          Latlon widget             //
////////////////////////////////////////
Drupal.gmap.addHandler('latlon', function (elem) {
    var obj = this;
    // Respond to incoming movements.
    var binding = obj.bind("move", function () {
        elem.value = '' + obj.vars.latitude + ',' + obj.vars.longitude;
    });
    // Send out outgoing movements.
    jQuery(elem).change(function () {
        var t = this.value.split(',');
        obj.vars.latitude = Number(t[0]);
        obj.vars.longitude = Number(t[1]);
        obj.change("move", binding);
    });
});

////////////////////////////////////////
//          Maptype widget            //
////////////////////////////////////////
Drupal.gmap.addHandler('maptype', function (elem) {
    var obj = this;
    // Respond to incoming movements.
    var binding = obj.bind("maptypechange", function () {
        elem.value = obj.vars.maptype;
    });
    // Send out outgoing movements.
    jQuery(elem).change(function () {
        obj.vars.maptype = elem.value;
        obj.change("maptypechange", binding);
    });
});

(function () { // BEGIN CLOSURE
    var re = /([0-9.]+)\s*(em|ex|px|in|cm|mm|pt|pc|%)/;
    var normalize = function (str) {
        var ar;
        if ((ar = re.exec(str.toLowerCase()))) {
            return ar[1] + ar[2];
        }
        return null;
    };

    ////////////////////////////////////////
    //           Width widget             //
    ////////////////////////////////////////
    Drupal.gmap.addHandler('width', function (elem) {
        var obj = this;
        // Respond to incoming width changes.
        var binding = obj.bind("widthchange", function (w) {
            elem.value = normalize(w);
        });
        // Send out outgoing width changes.
        jQuery(elem).change(function () {
            var n;
            if ((n = normalize(elem.value))) {
                elem.value = n;
                obj.change('widthchange', binding, n);
            }
        });
        obj.bind('init', function () {
            jQuery(elem).change();
        });
    });

    ////////////////////////////////////////
    //           Height widget            //
    ////////////////////////////////////////
    Drupal.gmap.addHandler('height', function (elem) {
        var obj = this;
        // Respond to incoming height changes.
        var binding = obj.bind("heightchange", function (h) {
            elem.value = normalize(h);
        });
        // Send out outgoing height changes.
        jQuery(elem).change(function () {
            var n;
            if ((n = normalize(elem.value))) {
                elem.value = n;
                obj.change('heightchange', binding, n);
            }
        });
        obj.bind('init', function () {
            jQuery(elem).change();
        });
    });
})(); // END CLOSURE

////////////////////////////////////////
//        Control type widget         //
////////////////////////////////////////
Drupal.gmap.addHandler('controltype', function (elem) {
    var obj = this;
    // Respond to incoming height changes.
    var binding = obj.bind("controltypechange", function () {
        elem.value = obj.vars.controltype;
    });
    // Send out outgoing height changes.
    jQuery(elem).change(function () {
        obj.vars.controltype = elem.value
        obj.change("controltypechange", binding);
    });
});

// // Map cleanup.
// if (Drupal.jsEnabled) {
//   $(document).unload(GUnload);
// }

Drupal.behaviors.GMap = {
    attach: function (context, settings) {
        if (Drupal.settings && Drupal.settings['gmap_remap_widgets']) {
            jQuery.each(Drupal.settings['gmap_remap_widgets'], function (key, val) {
                jQuery('#' + key).addClass('gmap-control');
            });
        }
        jQuery('.gmap-gmap:not(.gmap-processed)', context).addClass('gmap-processed').each(function () {
            Drupal.gmap.setup.call(this)
        });
        jQuery('.gmap-control:not(.gmap-processed)', context).addClass('gmap-processed').each(function () {
            Drupal.gmap.setup.call(this)
        });
    },
    detach: function (context, settings) {
        jQuery('.gmap-processed', context).each(function (element) {
            //find mapid
            var id = jQuery(this).attr('id');
            var mapid = id.split('-', 2);

            //unload map
            Drupal.gmap.unloadMap(mapid[1]);
        });
    }
};
